﻿using System;
using System.Activities;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YasharEl.Infrastructure.Core.Prototype;
using YasharEl.Infrastructure.Diagnostics.Logging;
using YasharEl.Infrastructure.ExceptionHandling;
using YasharEl.Infrastructure.ObjectFactory;
using YasharEl.Infrastructure.ProcessEngine.DomainModel.WorkflowManagement;
using YasharEl.Infrastructure.ProcessEngine.PersistenceModel.Repositories;
using YasharEl.Infrastructure.ProcessEngine.PersistenceModel.Specifications;
using YasharEl.Infrastructure.ResourcesManagement;
using YasharEl.Infrastructure.WorkflowModel.Management;

namespace YasharEl.Infrastructure.ProcessEngine.WorkflowModel
{
    public class ProcessEngineWorkflowDefinitionLoader : IWorkflowDefinitionLoader
    {
        #region ReadOnly Fields

        private readonly ILogger<ProcessEngineWorkflowDefinitionLoader> logger;
        private readonly IDependencyResolver resolver;
        private readonly IExceptionManager exceptionManager;
        private readonly IProcessesRepository processesRepository;
        private readonly IWorkflowActivityFinder workflowActivityFinder;

        #endregion

        #region Constructors

        public ProcessEngineWorkflowDefinitionLoader(
            ILogger<ProcessEngineWorkflowDefinitionLoader> logger,
            IDependencyResolver resolver,
            IExceptionManager exceptionManager,
            IProcessesRepository processesRepository,
            IWorkflowActivityFinder workflowActivityFinder)
        {
            Contract.Requires<ArgumentNullException>(logger != null, "logger could not be null.");
            Contract.Requires<ArgumentNullException>(resolver != null, "resolver could not be null.");
            Contract.Requires<ArgumentNullException>(exceptionManager != null, "exceptionManager could not be null.");
            Contract.Requires<ArgumentNullException>(processesRepository != null, "processesRepository could not be null.");
            Contract.Requires<ArgumentNullException>(workflowActivityFinder != null, "workflowActivityFinder could not be null.");

            this.logger = logger;
            this.resolver = resolver;
            this.exceptionManager = exceptionManager;
            this.processesRepository = processesRepository;
            this.workflowActivityFinder = workflowActivityFinder;
        }

        #endregion

        #region IWorkflowDefinitionLoader Members

        public Activity LoadWorkflowDefinition(Guid workflowInstanceId, out IDictionary<string, object> inputs)
        {
            logger.Info("Loading workflow definition {0}.", workflowInstanceId);
            inputs = new Dictionary<string, object>();
            IDictionary<string, DynamicActivityProperty> definedProperties = new Dictionary<string, DynamicActivityProperty>();
            Activity activity = null;
            try
            {
                IProcess process = processesRepository
                     .Specify<IProcessSpecification>()
                     .WithWorkflowInstanceId(workflowInstanceId)
                     .ToResult()
                     .SingleOrDefault();

                if (process == null)
                    throw new ProcessNotFoundException(
                        resolver.Resolve<IResourceManager>(),
                        workflowInstanceId
                    );

                activity = workflowActivityFinder.FindWorkflowProgram(process.ProcessDefinition.WorkflowXaml, out definedProperties);

                if (activity == null)
                {
                    throw new CouldNotFindWorkflowProgramException(
                        resolver.Resolve<IResourceManager>(), 
                        process.ProcessDefinition.WorkflowXaml
                    );
                }

                if (definedProperties != null && definedProperties.Count > 0)
                {
                    foreach (var property in definedProperties)
                    {
                        IPropertyInstance propertyInstance =
                            process.Properties.SingleOrDefault((i) => i.PropertyMapping.PropertyDefinition.Key == property.Key);
                        if (propertyInstance != null)
                        {
                            inputs.Add(
                                propertyInstance.PropertyMapping.PropertyDefinition.Key,
                                propertyInstance.ValueReference.RealValue
                            );
                        }
                    }
                }

                logger.Info("Workflow definition {0} successfully loaded.", workflowInstanceId);
            }
            catch (Exception e)
            {
                bool rethrow = exceptionManager.HandleException(e, ProcessEngineConstants.DomainLayerExceptionPolicy);
                if (rethrow)
                {
                    throw;
                }
            }

            return activity;
        }

        #endregion
    }
}
